'use strict'

// * 属性标志：对象属性，除属性值（value）外，还有三个特殊的特性，也就是所谓的标志（描述符）。
// * 1. writable 如果为 true，则值可以被修改，否则只可读。
// * 2. enumerable 如果为 true，则会被再循环中列出，否则不会被列出。
// * 3. configurable 如果为 true，则此属性可以被删除，前两项特性也可以被修改，否则不可以。
// * 默认都为true。

// *************************************************************************************************

// * 获取属性标志： Object.getOwnPropertyDescriptor(obj, prop) 返回一个“属性描述符”对象，它包含值和所有标志。
let user = {
	name: 'John',
}
let desc = Object.getOwnPropertyDescriptor(user, 'name')
console.log(JSON.stringify(desc, null, 2))
// {
//   "value": "John", 属性值
//   "writable": true, 属性值是否修改
//   "enumerable": true, 是否可枚举
//   "configurable": true 是否可删除 和 是否可修改特性
// }

// *************************************************************************************************

// * 修改属性标志：Object.defineProperty(obj, prop, desc) 如果属性不存在，则会使用提供的值和标志创建属性。
// * 如果没有提供标志，则标志默认都为false。
let obj = {}
Object.defineProperty(obj, 'name', {
	value: 'Jake',
})
let nameDesc = Object.getOwnPropertyDescriptor(obj, 'name')
console.log(JSON.stringify(nameDesc, null, 2))
// {
//   "value": "Jake",
//   "writable": false,
//   "enumerable": false,
//   "configurable": false
// }

console.log(obj.name)

// *************************************************************************************************

// * 只读

// 示例1
let objs = {
	name: 'OBJS',
}

Object.defineProperty(objs, 'name', {
	writable: false,
})

// objs.name = 'Pete' // 报错（只有在严格模式下才会报错；非严格模式下不报错，操作也不会成功）

// 示例2
let obj2 = {}

Object.defineProperty(obj2, 'name', {
	value: 'Jake',
	enumerable: true,
	configurable: true,
})

// obj2.name = 123 // 报错

// *************************************************************************************************

// * 不可枚举

let objs3 = {
	name: 'OBJS',
	toString() {
		return this.name
	},
}

for (const key in objs3) console.log(key) // name toString

Object.defineProperty(objs3, 'toString', {
	enumerable: false,
})

for (const key in objs3) console.log(key) // name
console.log(Object.keys(objs3)) // [ 'name' ]

// *************************************************************************************************

// * 不可删除 和 修改属性标志

// 例子1
let descPI = Object.getOwnPropertyDescriptor(Math, 'PI')
console.log(descPI)
// {
//   value: 3.141592653589793,
//   writable: false,
//   enumerable: false,
//   configurable: false
// }

// Math.PI = 123 // 报错

console.log(Object.keys(Math)) // [] 没有属性可迭代

// 报错，无法修改
// Object.defineProperty(Math, 'PI', {
// 	writable: true,
// 	configurable: true,
// })

// 例子2
let obj3 = {
	name: 'obj3',
}

Object.defineProperty(obj3, 'name', {
	configurable: false,
})

// delete obj3.name // 报错

obj3.name = 123 // 标志不可修改，属性值可修改
// console.log(obj3.name) // 123

Object.defineProperty(obj3, 'name', {
	writable: false,
	configurable: false,
})

// obj3.name = 666 // 报错
// Object.defineProperty(obj3, 'name', { value: 777 }) // 报错

// *************************************************************************************************

// * 对于 configurable = false 不可配置的属性，我们还是可以将 writable 改为 false，但无法再改回去。

let o = {
	name: '123',
}

// 属性不可删除，标志不可配置
Object.defineProperty(o, 'name', {
	configurable: false,
})

// 报错，无法再更改属性
// Object.defineProperty(o, 'name', {
// 	enumerable: false,
// })

// 但是可以将 writable 改为 false，然后不能再改回去
Object.defineProperty(o, 'name', {
	writable: false,
})

// 报错，无法再改回去
// Object.defineProperty(o, 'name', {
// 	writable: true,
// })

// *************************************************************************************************

// * Object.defineProperties 允许一次性定义多个属性及其标志（没有对应的属性就创建）

let newObj = {
	name: '小明',
}

Object.defineProperties(newObj, {
	name: {
		writable: false,
	},

	boy: {
		value: '王亚柠',
		writable: false,
	},

	gril: {
		value: '王茜',
		writable: false,
	},
})

console.log(newObj.name, newObj.boy, newObj.gril) // 小明 王亚柠 王茜
// newObj.name = 123 // 报错
// newObj.boy = 132 // 报错

// *************************************************************************************************

// * Object.getOwnPropertyDescriptors(obj) 一次性获取所有属性和描述符

let obj4 = {
	name: 'obj4',
	age: 27,
	skills: ['HTML', 'CSS', 'JS'],
}

let descs = Object.getOwnPropertyDescriptors(obj4)

console.log(descs)
// {
//   name: {
//     value: 'obj4',
//     writable: true,
//     enumerable: true,
//     configurable: true
//   },
//   age: { value: 27, writable: true, enumerable: true, configurable: true },
//   skills: {
//     value: [ 'HTML', 'CSS', 'JS' ],
//     writable: true,
//     enumerable: true,
//     configurable: true
//   }
// }

// * 使用场景：Object.getOwnPropertyDescriptors 拷贝对象，既拷贝属性值也拷贝属性描述符
let obj5 = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj4))
console.log(obj5) // { name: 'obj4', age: 27, skills: [ 'HTML', 'CSS', 'JS' ] }

// * 使用 for...in 的话，只能拿到属性值，不能拿到属性描述符。
// * forin 会忽略 symbol 类型和 不可枚举 属性，但是 Object.getOwnPropertyDescriptors 不会（它会返回所有 symbol 类型和 不可枚举 的属性及其描述符）。
// * 所以我们想要对一个对象进行完全完整的克隆，Object.defineProperties({}, Object.getOwnPropertyDescriptors) 是首选。

// *************************************************************************************************

// * 属性描述符在单个属性上工作，但还有一些限制整个对象的方法：
Object.preventExtensions(obj) // 禁止向对象添加新属性。
Object.seal(obj) // 禁止添加/删除属性。为现有所有属性设置 configurable 为 false。
Object.freeze(obj) // 禁止添加/删除/更改属性。为所有属性设置 writable 为 false 和 configurable 为 false。
Object.isExtensible(obj) // 如果添加属性被禁止，则返回 false，否则返回 true。
Object.isSealed(obj) // 如果添加/删除属性被禁止，并且所有现有的属性都具有 configurable: false 则返回 true。
Object.isFrozen(obj) // 如果添加/删除/更改属性被禁止，并且所有当前属性都是 configurable: false, writable: false 则返回 true。
